powerpointproto
slides.waow.tech
slides
1const DECK_COLLECTION = "tech.waow.slides.deck";
2
3async function resolveHandleToDid(handle: string): Promise<string | null> {
4 // strip @ if present
5 const cleanHandle = handle.replace(/^@/, "");
6
7 try {
8 const res = await fetch(
9 `https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(cleanHandle)}`
10 );
11 if (res.ok) {
12 const data = await res.json();
13 return data.did || null;
14 }
15 } catch {}
16 return null;
17}
18
19async function resolvePds(did: string): Promise<string> {
20 try {
21 if (did.startsWith("did:plc:")) {
22 const res = await fetch(`https://plc.directory/${did}`);
23 if (res.ok) {
24 const doc = await res.json();
25 const pds = doc.service?.find(
26 (s: { id: string; serviceEndpoint?: string }) => s.id === "#atproto_pds"
27 );
28 if (pds?.serviceEndpoint) return pds.serviceEndpoint;
29 }
30 }
31 } catch {}
32 return "https://bsky.social";
33}
34
35type DeckRecord = {
36 name: string;
37 slides: Array<{ subject: { uri: string; cid: string } }>;
38 createdAt: string;
39 updatedAt?: string;
40 thumbnail?: {
41 ref: { $link: string };
42 mimeType: string;
43 };
44};
45
46async function listDecks(did: string) {
47 const pdsUrl = (await resolvePds(did)).replace(/\/$/, "");
48
49 try {
50 const res = await fetch(
51 `${pdsUrl}/xrpc/com.atproto.repo.listRecords?repo=${did}&collection=${DECK_COLLECTION}&limit=100`
52 );
53 if (!res.ok) return [];
54 const data = await res.json();
55
56 // eslint-disable-next-line @typescript-eslint/no-explicit-any
57 return data.records.map((r: any) => {
58 const val = r.value as DeckRecord;
59 const rkey = r.uri.split("/").pop()!;
60 const thumbnailUrl = val.thumbnail
61 ? `${pdsUrl}/xrpc/com.atproto.sync.getBlob?did=${encodeURIComponent(did)}&cid=${encodeURIComponent(val.thumbnail.ref.$link)}`
62 : null;
63 return {
64 uri: r.uri,
65 rkey,
66 name: val.name,
67 slideCount: val.slides?.length || 0,
68 createdAt: val.createdAt,
69 updatedAt: val.updatedAt,
70 thumbnailUrl,
71 };
72 });
73 } catch {
74 return [];
75 }
76}
77
78export const load = async ({ params }) => {
79 const { handle } = params;
80 const cleanHandle = handle.replace(/^@/, "");
81
82 const did = await resolveHandleToDid(cleanHandle);
83 if (!did) {
84 return {
85 handle: cleanHandle,
86 did: null,
87 decks: [],
88 error: "user not found",
89 };
90 }
91
92 const decks = await listDecks(did);
93
94 return {
95 handle: cleanHandle,
96 did,
97 decks,
98 error: null,
99 };
100};